//
// Created on 2024/6/6.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include <native_window/external_window.h>
#include <thread>
#include "hilog/log.h"
#include "ohos_eglContext_manage.h"
#include "ohos_video_encoder.h"
#include "ohos_desktop.h"
#include "util.h"

namespace webrtc {
namespace ohos {

OhosVideoEncoder::~OhosVideoEncoder()
{
    Release();
}

int32_t OhosVideoEncoder::Create() {
    if (encoder_ != nullptr) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "Encoder had been created");
        return AV_ERR_OK;
    }
    OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
    bool isHardware = OH_AVCapability_IsHardware(capability);
    if (!isHardware) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "Hardware Not Support");
    }
    const char *codecName = OH_AVCapability_GetName(capability);
    encoder_ = OH_VideoEncoder_CreateByName(codecName);
    if (encoder_ == nullptr) {
        return AV_ERR_UNKNOWN;
    }
    return AV_ERR_OK;
}

int32_t OhosVideoEncoder::Config() {
    int32_t ret = AV_ERR_INVALID_VAL;
    if (encoder_ == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "encoder_ nullptr");
        return AV_ERR_UNKNOWN;
    }
    if (formatInfo.videoHeight == 0 || formatInfo.videoWidth == 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "Configure Parameter Invalid");
        return AV_ERR_INVALID_VAL;
    }
    OH_AVFormat *format = OH_AVFormat_Create();
    if (format == nullptr) {
        return AV_ERR_UNKNOWN;
    }
    // 写入format
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, formatInfo.videoWidth);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, formatInfo.videoHeight);
    OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, formatInfo.frameRate);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, formatInfo.pixelFormat);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_RANGE_FLAG, formatInfo.rangeFlag);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_COLOR_PRIMARIES, formatInfo.primary);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_TRANSFER_CHARACTERISTICS, formatInfo.transfer);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_MATRIX_COEFFICIENTS, formatInfo.matrix);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, formatInfo.rateMode);
    OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, formatInfo.bitrate);
    ret = OH_VideoEncoder_Configure(encoder_, format);
    if (ret != AV_ERR_OK) {
        // 异常处理
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OH_VideoEncoder_Configure Failed");
        return ret;
    } else {
        OH_AVFormat_Destroy(format);
        format = nullptr;
    }
    ret = OH_VideoEncoder_GetSurface(encoder_, &nativeWindow_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "GetSurface failed");
    }
    //设置nativewindow的宽高属性，nativewindow需要在使用完后调用destroy
    if(nativeWindow_ != nullptr) {
        OH_NativeWindow_NativeWindowHandleOpt(nativeWindow_, SET_BUFFER_GEOMETRY, 1216, 2688);
    }
    ret = SetCallback();
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "SetCallback failed");
        return ret;
    }
    ret = OH_VideoEncoder_Prepare(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OH_VideoEncoder_Prepare Failed");
        return ret;
    }
    
    stat = EncodeStat::CONFIGUED;
    return ret;
}

int32_t OhosVideoEncoder::Start() {
    if (encoder_ == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OhosVideoEncoder nullptr");
        return AV_ERR_INVALID_VAL;
    }
    // 启动编码器，开始编码
    int32_t ret = OH_VideoEncoder_Start(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OH_VideoEncoder_Start Failed");
        return ret;
    }
    stat = EncodeStat::RUNNING;
    eosFlag_.store(false);
    return ret;
}

int32_t OhosVideoEncoder::Stop() {
    if (encoder_ == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OhosVideoEncoder nullptr");
        return AV_ERR_INVALID_VAL;
    }
    int32_t ret = AV_ERR_INVALID_VAL;
    ret = OH_VideoEncoder_Stop(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OH_VideoEncoder_Stop Failed");
        return ret;
    }
    stat = EncodeStat::STOP;
    return AV_ERR_OK;
}

void OhosVideoEncoder::Release() {
    int32_t ret = AV_ERR_OK;
    drawThread_->join();
    Stop();
    eglRenderContext_.reset();
    DestroyNativeWindow();
    if (encoder_ != nullptr) {
        ret = OH_VideoEncoder_Destroy(encoder_);
        encoder_ = nullptr;
        stat = EncodeStat::RELEASE;
    }
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OH_VideoEncoder_Destroy Failed");
    }
}

void OhosVideoEncoder::OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{
    // 回调的错误码由用户判断处理
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OhosVideoEncoder::OnError");
    (void)codec;
    (void)errorCode;
    (void)userData;
}

void OhosVideoEncoder::OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
{
    // 编码场景，该回调函数无作用
    (void)codec;
    (void)format;
    (void)userData;
}

void OhosVideoEncoder::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
    // surface模式下，该回调函数无作用，用户通过获取的surface输入数据
    (void)userData;
    (void)index;
    (void)buffer;
}

// 设置 OH_AVCodecOnNewOutputBuffer 回调函数，编码完成帧送入输出队列
void OhosVideoEncoder::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
    if (userData == nullptr) {
        return;
    }
    OhosVideoEncoder *ohosVideoEncoder = static_cast<OhosVideoEncoder *>(userData);
    // 获取编码后信息
    OH_AVCodecBufferAttr info;
    int32_t ret = OH_AVBuffer_GetBufferAttr(buffer, &info);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OnNewOutputBuffer GetBufferAttr Failed");
    }
    //TODO read data
    FILE *fp = nullptr;
    fp = fopen("/data/storage/el2/base/files/desktop.h264", "a");
    fwrite(OH_AVBuffer_GetAddr(buffer), sizeof(char), info.size, fp);
    fclose(fp);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "OutputBuffer len:%{public}d", info.size);
    if(info.flags != AVCODEC_BUFFER_FLAGS_CODEC_DATA) {
        if(info.flags == AVCODEC_BUFFER_FLAGS_EOS) {
            ohosVideoEncoder->eosFlag_.store(true);
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "OutputBuffer over!");
        } else {
            OhosEGLContextManage::GetInstance().AddOutputCnt();
            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "OutputCnt :%{public}d",
                         OhosEGLContextManage::GetInstance().GetOutputCnt());
        }
    } else {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosVideoEncoder", "info.flags:AVCODEC_BUFFER_FLAGS_CODEC_DATA");
    }
    ret = OH_VideoEncoder_FreeOutputBuffer(ohosVideoEncoder->encoder_, index);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "OnNewOutputBuffer FreeOutputBuffer Failed");
    }
}

int32_t OhosVideoEncoder::SetCallback() {
    // 配置异步回调，调用 OH_VideoEncoder_SetCallback 接口
    OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
    int32_t ret = OH_VideoEncoder_RegisterCallback(encoder_, cb, this);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "SetCallback Failed");
        return ret;
    }
    return ret;
}

void OhosVideoEncoder::StartThread() {
    drawThread_ = std::make_unique<std::thread>(&OhosVideoEncoder::DrawThread, this);
}

void OhosVideoEncoder::DrawThread() {
    eglRenderContext_ = std::make_unique<EglRenderContext>();
    if (eglRenderContext_->Init(OhosEGLContextManage::GetInstance().GetEGLContext()) == false) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "EglRenderContext Init() failed");
    }
    
    if (eglSurface_ == EGL_NO_SURFACE) {
        eglSurface_ = eglRenderContext_->CreateEglSurface(static_cast<EGLNativeWindowType>(nativeWindow_));
    }
    if (eglSurface_ == EGL_NO_SURFACE) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "CreateEglSurface %{public}s ",
                     GetEglErrorString());
    }
    eglRenderContext_->MakeCurrent(eglSurface_); //绘制开始前切换上下文
    
    OhosGLDrawer_ = std::make_unique<OhosGLDrawer>();
    while (OhosDesktop::GetIsRunning()) {
        std::unique_lock<std::mutex> lock(OhosEGLContextManage::GetInstance().GetMutex());
        OhosEGLContextManage::GetInstance().GetConditionVariable().wait(lock, [this]() {
            return !OhosEGLContextManage::GetInstance().TextureIDQueueEmpty() || !OhosDesktop::GetIsRunning();
        });
        if (!OhosDesktop::GetIsRunning()) {
            break;
        }
        TextureBuffer textureBuffer;
        textureBuffer.frameType = TextureType::OES;
        textureBuffer.textureID = OhosEGLContextManage::GetInstance().PopTextureID();
        OhosGLDrawer_->CreateGLResources();
        OhosGLDrawer_->DrawFrame(textureBuffer, 0, 0, 0, 0, 0, 1216, 2688);
        eglRenderContext_->SwapBuffers(eglSurface_);
        OhosGLDrawer_->CleanGLResources();
    }
    int32_t ret = OH_VideoEncoder_NotifyEndOfStream(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosVideoEncoder", "NotifyEndOfStream Failed");
    }
    while (!eosFlag_.load());
    eglRenderContext_->MakeNoCurrent();         //绘制结束解绑上下文
    eglRenderContext_.reset();
}

void OhosVideoEncoder::DestroyNativeWindow() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeWindow started");
    if (nativeWindow_ != nullptr) {
        OH_NativeWindow_DestroyNativeWindow(nativeWindow_);
        nativeWindow_ = nullptr;
    }
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeWindow end");
}

}
}
